﻿using System;
using System.Collections;
using System.Collections.Generic;

using System.Text;
using System.Data;
using Common.Logging;
using Efficient.Domain;

namespace Efficient.DataAccess
{
    internal class QueryImpl : IQuery
    {
        protected static readonly ILog logger = LogManager.GetLogger(typeof(QueryImpl));
        protected readonly IDbService dataService;
        protected Delegate converter;
        protected readonly string queryString;
        protected CommandType cmdType = CommandType.Text;
        protected List<IQueryParameter> paramList = new List<IQueryParameter>();
        protected uint firstResult = 0;
        protected uint maxResults;
        protected IDbConnection existConn;
        protected IDbTransaction existTran;
        protected readonly string startParamName;
        protected readonly string endParamName;
        protected bool sqlPaging = true;
        protected const string ORDER_BY = " order by "; 

        public QueryImpl(IDbService dataService, string query)
        {
            this.queryString = query;
            this.dataService = dataService;
            startParamName = string.Concat(dataService.ParamNamedPrefix, "PagingStart__");
            endParamName = string.Concat(dataService.ParamNamedPrefix, "PagingEnd__");
        }

        public QueryImpl(IDbConnection existConn, IDbService dataService, string query)
            : this(dataService, query)
        {
            this.existConn = existConn;
        }

        public QueryImpl(IDbTransaction existTran, IDbService dataService, string query)
            : this(dataService, query)
        {
            this.existTran = existTran;
        }
        #region IQuery Members

        public object ExecuteScalar()
        {
            LogRawSqlBeforeExecute();
            string query = this.queryString.Clone() as string;
            ParameterResult result = this.GetCurrentParameter(query);
            LogParseSqlBeforeExecute(result.Query);
            return ExecuteScalar(this.cmdType, result.Query, result.Parameters);
        }

        public int ExecuteUpdate()
        {
            LogRawSqlBeforeExecute();
            string query = this.queryString.Clone() as string;
            ParameterResult result = this.GetCurrentParameter(query);
            LogParseSqlBeforeExecute(result.Query);
            return ExecuteNonQuery(this.cmdType, result.Query, result.Parameters);
        }

        public IList<T> List<T>() where T:new()
        {
            return List((Reader2Entity<T>)this.converter);
        }

        public IList<T> List<T>(Reader2Entity<T> converter) where T : new()
        {
            IList<T> result = new List<T>();
            this.List(result, converter);
            return result;
        }

        public DataTable Table()
        {
            return Table(string.Empty);
        }

        public DataTable Table(string name)
        {
            LogRawSqlBeforeExecute();
            string query = BuildQueryWhenFetchData().Clone() as string;
            ParameterResult result = this.GetCurrentParameter(true, query);
            LogParseSqlBeforeExecute(result.Query);
            DataTable dt = this.ExecuteDataTable(this.cmdType, result.Query, result.Parameters);
            if (!string.IsNullOrEmpty(name))
            {
                dt.TableName = name;
            }
            return dt;
        }

        private static void LogParseSqlBeforeExecute(string sql)
        {
            if (logger.IsDebugEnabled)
            {
                logger.Debug("真正运行的SQL:" + sql);
            }
        }
        private void LogRawSqlBeforeExecute()
        {
            if (logger.IsDebugEnabled)
            {
                logger.Debug("原始的SQL:" + this.queryString);
            }
        }

        public void List<T>(IList<T> results) where T : new()
        {
            List(results, (Reader2Entity<T>)this.converter);
        }

        public void List<T>(IList<T> results, Reader2Entity<T> converter) where T : new()
        {
            CheckEntityConverter(converter);
            if (results == null)
            {
                results = new List<T>();
            }
            LogRawSqlBeforeExecute();
            string readQuery = BuildQueryWhenFetchData().Clone() as string;
            ParameterResult param = this.GetCurrentParameter(true, readQuery);
            LogParseSqlBeforeExecute(param.Query);
            using (IDataReader reader = this.ExecuteReader(this.cmdType, param.Query, param.Parameters))
            {
                ExtendDataReader.FillList(reader, results, converter, this.sqlPaging ? 0 : this.firstResult, this.maxResults);
                reader.Close();
            }
        }

        

        public T UniqueResult<T>() where T : new()
        {
            return UniqueResult((Reader2Entity<T>)this.converter);
        }

        public T UniqueResult<T>(Reader2Entity<T> converter) where T : new()
        {
            CheckEntityConverter(converter);
            T result = default(T);
            LogRawSqlBeforeExecute();
            string query = this.queryString.Clone() as string;
            ParameterResult param = this.GetCurrentParameter(query);
            LogParseSqlBeforeExecute(param.Query);
            using (IDataReader reader = this.ExecuteReader(this.cmdType, param.Query, param.Parameters))
            {
                ExtendDataReader.FillEntity(reader, ref result, converter);
                reader.Close();
            }
            return result;
        }

        public long ComputeRecordCount()
        {
            if (!this.IsSelectQuery())
            {
                throw new NotSupportedException("这个查询不是一个简单的select语句,不能自动统计查询返回记录数");
            }
            LogRawSqlBeforeExecute();
            string query = this.queryString.Clone() as string;
            ParameterResult param = this.GetCurrentParameter(query);
            if (!param.Query.Equals(query, StringComparison.OrdinalIgnoreCase))
            {
                query = param.Query;
            }
            int orderByIndex = query.LastIndexOf(ORDER_BY, StringComparison.OrdinalIgnoreCase);
            if (orderByIndex > 0)
            {
                query = query.Substring(0, orderByIndex);
            }
            string countSql = dataService.BuildComputeCountQuery(query);
            LogParseSqlBeforeExecute(countSql);
            object result = this.ExecuteScalar(CommandType.Text, countSql, param.Parameters);
            return result == null ? 0 : Convert.ToInt64(result);
        }

        public IQuery SetCommandType(CommandType cmdType)
        {
            this.cmdType = cmdType;
            return this;
        }

        public IQuery SetEntityConverter<T>(Reader2Entity<T> converter) where T:new()
        {
            this.converter = converter;
            return this;
        }

        public IQuery SetEnableSqlPaging(bool enable)
        {
            this.sqlPaging = enable;
            return this;
        }

        public IQuery SetBinary(string name, byte[] val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetBoolean(string name, bool val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetByte(string name, byte val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetCharacter(string name, char val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetDateTime(string name, DateTime val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetDecimal(string name, decimal val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetDouble(string name, double val)
        {
            SetParameterValue(name, val);
            return this;
        }

        

        public IQuery SetGuid(string name, Guid val)
        {
            SetParameterValue(name, val.ToString());
            return this;
        }

        public IQuery SetInt16(string name, short val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetInt32(string name, int val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetInt64(string name, long val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetFirstResult(uint firstResult)
        {
            this.firstResult = firstResult;
            return this;
        }

        public IQuery SetMaxResults(uint maxResults)
        {
            this.maxResults = maxResults;
            return this;
        }

        public IQuery SetParameter(string name, object val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetParameterList(string name, object[] vals)
        {
            SetParameterValue(name, vals, true);
            return this;
        }

        public IQuery SetParameterList<T>(string name, ICollection<T> vals)
        {
            if (vals == null || vals.Count == 0)
            {
                throw new ArgumentException("集合变量的参数为null或者集合中没有项");
            }
            SetParameterValue(name, vals, true);
            return this;
        }

        public IQuery SetSingle(string name, float val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetString(string name, string val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetTime(string name, DateTime val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetTimestamp(string name, DateTime val)
        {
            SetParameterValue(name, val);
            return this;
        }

        public IQuery SetQueryParameters(IEnumerable<IParameter> paramSet)
        {
            if (paramSet == null)
            {
                throw new NullReferenceException();
            }
            foreach (IParameter item in paramSet)
            {
                this.SetParameter(item.Name, item.Value);
            }
            return this;
        }

        #endregion

        #region 内部辅助函数

        private IDataReader ExecuteReader(CommandType cmdType, string cmdText, IDbDataParameter[] parameter)
        {
            if (existTran != null)
            {
                return dataService.ExecuteReader(existTran, cmdType, cmdText, parameter);
            }
            if (existConn != null)
            {
                return dataService.ExecuteReader(existConn, cmdType, cmdText, parameter);
            }
            return dataService.ExecuteReader(cmdType, cmdText, parameter);
        }

        private int ExecuteNonQuery(CommandType cmdType, string cmdText, IDbDataParameter[] parameter)
        {
            if (existTran != null)
            {
                return dataService.ExecuteNonQuery(existTran, cmdType, cmdText, parameter);
            }
            if (existConn != null)
            {
                return dataService.ExecuteNonQuery(existConn, cmdType, cmdText, parameter);
            }
            return dataService.ExecuteNonQuery(cmdType, cmdText, parameter);
        }

        private object ExecuteScalar(CommandType cmdType, string cmdText, IDbDataParameter[] parameter)
        {
            if (existTran != null)
            {
                return dataService.ExecuteScalar(existTran, cmdType, cmdText, parameter);
            }
            if (existConn != null)
            {
                return dataService.ExecuteScalar(existConn, cmdType, cmdText, parameter);
            }
            return dataService.ExecuteScalar(cmdType, cmdText, parameter);
        }

        private DataTable ExecuteDataTable(CommandType cmdType, string cmdText, IDbDataParameter[] parameter)
        {
            if (existTran != null)
            {
                return dataService.ExecuteDataTable(existTran, cmdType, cmdText, parameter);
            }
            if (existConn != null)
            {
                return dataService.ExecuteDataTable(existConn, cmdType, cmdText, parameter);
            }
            return dataService.ExecuteDataTable(cmdType, cmdText, parameter);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="query"></param>
        /// <returns>数组第一个元素是查询字符串，后面的是SQL参数数组</returns>
        private ParameterResult GetCurrentParameter(string query)
        {
            return GetCurrentParameter(false, query);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="needPaging"></param>
        /// <param name="query"></param>
        /// <returns>数组第一个元素是查询字符串，后面的是SQL参数数组</returns>
        private ParameterResult GetCurrentParameter(bool needPaging, string query) 
        {
            string resultQuery = query;
            List<IDbDataParameter> result = new List<IDbDataParameter>();
            foreach (IQueryParameter paramItem in paramList)
            {
                result.AddRange(paramItem.ToDbParameter(ref resultQuery));
            }
            if (needPaging && this.IsFetchStart() && this.IsSelectQuery())
            {
                IDbDataParameter paramStart = dataService.CreateParameter(this.startParamName, ParameterDirection.Input, DbType.Int32, this.firstResult);
                result.Add(paramStart);
            }
            if (needPaging && this.IsNeedPaging() && this.IsSelectQuery())
            {
                IDbDataParameter paramStart = dataService.CreateParameter(this.startParamName, ParameterDirection.Input, DbType.Int32, this.firstResult);
                result.Add(paramStart);
                IDbDataParameter paramEnd = dataService.CreateParameter(this.endParamName, ParameterDirection.Input, DbType.Int32, this.maxResults + this.firstResult);
                result.Add(paramEnd);
            }
            return new ParameterResult(result.ToArray(), resultQuery);
        }

        /// <summary>
        /// 初始化dbCommand对象
        /// </summary>
        /// <param name="dbCommand">要初始化的命令</param>
        /// <param name="connection">数据库连接</param>
        /// <param name="trans">数据库事务</param>
        /// <param name="cmdType">命令类型</param>
        /// <param name="cmdText">命令语句</param>
        /// <param name="commandParameters">参数</param>
        protected virtual void PrepareCommand(IDbCommand dbCommand, IDbConnection connection, IDbTransaction trans, CommandType cmdType,
                                              string cmdText, params IDbDataParameter[] commandParameters)
        {
            if (connection.State != ConnectionState.Open)
            {
                connection.Open();
            }

            //Set up the command
            dbCommand.Connection = connection;
            dbCommand.CommandText = cmdText;
            dbCommand.CommandType = cmdType;

            //Bind it to the transaction if it exists
            if (trans != null)
            {
                dbCommand.Transaction = trans;
            }
            dataService.AttachParameter(dbCommand, commandParameters);
        }

        protected void SetParameterValue(string name, object value)
        {
            SetParameterValue(name, value, false);
        }

        protected virtual void SetParameterValue(string name, object value, bool isMutilValue)
        {
            if (!name.StartsWith(this.dataService.ParamNamedPrefix))
            {
                name = string.Concat(this.dataService.ParamNamedPrefix, name);
            }
            IQueryParameter entity = this.paramList.Find(delegate(IQueryParameter item) 
                                                             {
                                                                 return item.Name.Equals(name, StringComparison.OrdinalIgnoreCase);
                                                             });
            if (entity == null)
            {
                entity = new QueryParameter(this.dataService);
                this.paramList.Add(entity);
            }
            entity.Name = name;
            entity.IsMutilValue = isMutilValue;
            if (entity.IsMutilValue && value is IEnumerable)
            {
                entity.SetValues((IEnumerable)value);
            }
            else
            {
                entity.Value = value;
            }
        }

        private static void CheckEntityConverter(Delegate converter)
        {
            if (converter == null)
            {
                throw new NullReferenceException("实体转换器为空");
            }
        } 

        private bool IsSelectQuery()
        {
            return this.cmdType == CommandType.Text && this.queryString.Trim().StartsWith("select ", StringComparison.OrdinalIgnoreCase);
        }

        private string BuildMaxResultQuery(string rawQuery)
        {
            if (dataService.SupportLimit)
            {
                return dataService.MaxResultQuery(rawQuery, this.maxResults);
            }
            return rawQuery;
        }

        private string BuildFetchFromQuery(string rawQuery)
        {
            if (dataService.SupportLimitOffset)
            {
                if (dataService.PagingNeedOrder)
                {
                    int orderIndex = rawQuery.LastIndexOf(ORDER_BY, StringComparison.OrdinalIgnoreCase);
                    if (orderIndex == -1)
                    {
                        throw new ArgumentException("语句要进行分页必须指定排序");
                    }
                    string rawOrderString = rawQuery.Substring(orderIndex + ORDER_BY.Length).Trim();
                    rawOrderString = ProcessOrderString(rawOrderString);
                    return dataService.BuildFirstResultQueryNeedOrder(rawQuery.Substring(0, orderIndex), rawOrderString, this.startParamName);
                }
                return dataService.BuildFirstResultQuery(rawQuery, this.startParamName);
            }
            return rawQuery;
        }

        private string BuildPagingQuery(string rawQuery)
        {
            if (dataService.SupportLimitOffset)
            {
                if (dataService.PagingNeedOrder)
                {
                    int orderIndex = rawQuery.LastIndexOf(ORDER_BY, StringComparison.OrdinalIgnoreCase);
                    if (orderIndex == -1)
                    {
                        throw new ArgumentException("语句要进行分页必须指定排序");
                    }
                    string rawOrderString = rawQuery.Substring(orderIndex + ORDER_BY.Length).Trim();
                    rawOrderString = ProcessOrderString(rawOrderString);
                    return dataService.BuildPagingResultQueryNeedOrder(rawQuery.Substring(0, orderIndex), rawOrderString, this.startParamName, this.endParamName);
                }
                return dataService.BuildPagingResultQuery(rawQuery, this.startParamName, this.endParamName);
            }
            return rawQuery;
        }

        private static string ProcessOrderString(string rawOrderString)
        {
            string[] orderFiled = rawOrderString.Split(',');
            for (int i = 0; i < orderFiled.Length; i++)
            {
                string item = orderFiled[i];
                string[] itemArray = item.Split(' ');
                for (int j = 0; j < itemArray.Length; j++)
                { 
                    int pointIndex = itemArray[j].IndexOf('.');
                    if (pointIndex > 0)
                    {
                        itemArray[j] = itemArray[j].Substring(pointIndex + 1);
                    }
                }
                orderFiled[i] = string.Join(" ", itemArray);
            }
            return string.Join(", ", orderFiled);
        }

        private string BuildQueryWhenFetchData()
        {
            if (this.IsNeedMax())
            {
                return this.BuildMaxResultQuery(this.queryString);
            }
            if (this.IsFetchStart())
            {
                return this.BuildFetchFromQuery(this.queryString);
            }
            if (this.IsNeedPaging())
            {
                return this.BuildPagingQuery(this.queryString);
            }

            return this.queryString;
        }

        

        private bool IsFetchStart()
        {
            return this.sqlPaging && this.firstResult > 0 && this.maxResults == 0;
        }
        private bool IsNeedMax()
        {
            return this.sqlPaging && this.maxResults > 0 && this.firstResult == 0; 
        }

        private bool IsNeedPaging()
        {
            return this.sqlPaging && this.maxResults > 0 && this.firstResult > 0;
        }

        private class ParameterResult
        {
            private readonly IDbDataParameter[] parameters;
            private readonly string query;

            public IDbDataParameter[] Parameters
            {
                get { return parameters; }
            }

            public string Query
            {
                get { return query; }
            }

            public ParameterResult(IDbDataParameter[] parameters, string query)
            {
                this.parameters = parameters;
                this.query = query;
            }

        }
        
        #endregion
    }
}
